package trafficpolicy

import (
	"testing"

	envoycorev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	envoy_ext_proc_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	gwv1 "sigs.k8s.io/gateway-api/apis/v1"

	"github.com/kgateway-dev/kgateway/v2/api/v1alpha1/kgateway"
	"github.com/kgateway-dev/kgateway/v2/pkg/pluginsdk/ir"
)

func TestExtprocIREquals(t *testing.T) {
	createSimpleExtproc := func(requestMode envoy_ext_proc_v3.ProcessingMode_HeaderSendMode) *envoy_ext_proc_v3.ExtProcPerRoute {
		return &envoy_ext_proc_v3.ExtProcPerRoute{
			Override: &envoy_ext_proc_v3.ExtProcPerRoute_Overrides{
				Overrides: &envoy_ext_proc_v3.ExtProcOverrides{
					ProcessingMode: &envoy_ext_proc_v3.ProcessingMode{
						RequestHeaderMode: requestMode,
					},
				},
			},
		}
	}
	createProvider := func(name string) *TrafficPolicyGatewayExtensionIR {
		return &TrafficPolicyGatewayExtensionIR{
			Name: name,
			ExtProc: buildCompositeExtProcFilter(
				kgateway.ExtProcProvider{FailOpen: true},
				&envoycorev3.GrpcService{
					TargetSpecifier: &envoycorev3.GrpcService_EnvoyGrpc_{
						EnvoyGrpc: &envoycorev3.GrpcService_EnvoyGrpc{
							ClusterName: name,
						},
					},
				},
			),
		}
	}

	tests := []struct {
		name     string
		extproc1 *extprocIR
		extproc2 *extprocIR
		expected bool
	}{
		{
			name:     "both nil are equal",
			extproc1: nil,
			extproc2: nil,
			expected: true,
		},
		{
			name:     "nil vs non-nil are not equal",
			extproc1: nil,
			extproc2: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: createSimpleExtproc(envoy_ext_proc_v3.ProcessingMode_SEND)}}},
			expected: false,
		},
		{
			name:     "non-nil vs nil are not equal",
			extproc1: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: createSimpleExtproc(envoy_ext_proc_v3.ProcessingMode_SEND)}}},
			extproc2: nil,
			expected: false,
		},
		{
			name:     "same instance is equal",
			extproc1: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: createSimpleExtproc(envoy_ext_proc_v3.ProcessingMode_SEND)}}},
			extproc2: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: createSimpleExtproc(envoy_ext_proc_v3.ProcessingMode_SEND)}}},
			expected: true,
		},
		{
			name:     "different processing modes are not equal",
			extproc1: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: createSimpleExtproc(envoy_ext_proc_v3.ProcessingMode_SEND)}}},
			extproc2: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: createSimpleExtproc(envoy_ext_proc_v3.ProcessingMode_SKIP)}}},
			expected: false,
		},
		{
			name:     "different providers are not equal",
			extproc1: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{provider: createProvider("service1")}}},
			extproc2: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{provider: createProvider("service2")}}},
			expected: false,
		},
		{
			name:     "same providers are equal",
			extproc1: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{provider: createProvider("service1")}}},
			extproc2: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{provider: createProvider("service1")}}},
			expected: true,
		},
		{
			name:     "nil perRoute fields are equal",
			extproc1: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: nil}}},
			extproc2: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: nil}}},
			expected: true,
		},
		{
			name:     "nil vs non-nil perRoute fields are not equal",
			extproc1: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: nil}}},
			extproc2: &extprocIR{perProviderConfig: []*perProviderExtProcConfig{{perRouteConfig: createSimpleExtproc(envoy_ext_proc_v3.ProcessingMode_SEND)}}},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.extproc1.Equals(tt.extproc2)
			assert.Equal(t, tt.expected, result)

			// Test symmetry: a.Equals(b) should equal b.Equals(a)
			reverseResult := tt.extproc2.Equals(tt.extproc1)
			assert.Equal(t, result, reverseResult, "Equals should be symmetric")
		})
	}
}

func TestBuildEnvoyExtProc(t *testing.T) {
	tests := []struct {
		name           string
		gatewayExt     *ir.GatewayExtension
		extprocConfig  *kgateway.ExtProcPolicy
		expectedError  string
		validateResult func(*testing.T, *envoy_ext_proc_v3.ExtProcPerRoute)
	}{
		{
			name: "with all processing modes",
			gatewayExt: &ir.GatewayExtension{
				ExtProc: &kgateway.ExtProcProvider{
					GrpcService: kgateway.ExtGrpcService{
						BackendRef: gwv1.BackendRef{
							BackendObjectReference: gwv1.BackendObjectReference{
								Name: "test-service",
							},
						},
					},
				},
			},
			extprocConfig: &kgateway.ExtProcPolicy{
				ProcessingMode: &kgateway.ProcessingMode{
					RequestHeaderMode:   "SEND",
					ResponseHeaderMode:  "SKIP",
					RequestBodyMode:     "STREAMED",
					ResponseBodyMode:    "BUFFERED",
					RequestTrailerMode:  "SEND",
					ResponseTrailerMode: "SKIP",
				},
			},
			validateResult: func(t *testing.T, result *envoy_ext_proc_v3.ExtProcPerRoute) {
				processingMode := result.GetOverrides().GetProcessingMode()
				assert.NotNil(t, processingMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_SEND, processingMode.RequestHeaderMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_SKIP, processingMode.ResponseHeaderMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_STREAMED, processingMode.RequestBodyMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_BUFFERED, processingMode.ResponseBodyMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_SEND, processingMode.RequestTrailerMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_SKIP, processingMode.ResponseTrailerMode)
			},
		},
		{
			name: "with default processing modes",
			gatewayExt: &ir.GatewayExtension{
				ExtProc: &kgateway.ExtProcProvider{
					GrpcService: kgateway.ExtGrpcService{
						BackendRef: gwv1.BackendRef{
							BackendObjectReference: gwv1.BackendObjectReference{
								Name: "test-service",
							},
						},
					},
				},
			},
			extprocConfig: &kgateway.ExtProcPolicy{
				ProcessingMode: &kgateway.ProcessingMode{},
			},
			validateResult: func(t *testing.T, result *envoy_ext_proc_v3.ExtProcPerRoute) {
				processingMode := result.GetOverrides().GetProcessingMode()
				assert.NotNil(t, processingMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_DEFAULT, processingMode.RequestHeaderMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_DEFAULT, processingMode.ResponseHeaderMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_NONE, processingMode.RequestBodyMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_NONE, processingMode.ResponseBodyMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_DEFAULT, processingMode.RequestTrailerMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_DEFAULT, processingMode.ResponseTrailerMode)
			},
		},
		{
			name: "with invalid processing modes",
			gatewayExt: &ir.GatewayExtension{
				ExtProc: &kgateway.ExtProcProvider{
					GrpcService: kgateway.ExtGrpcService{
						BackendRef: gwv1.BackendRef{
							BackendObjectReference: gwv1.BackendObjectReference{
								Name: "test-service",
							},
						},
					},
				},
			},
			extprocConfig: &kgateway.ExtProcPolicy{
				ProcessingMode: &kgateway.ProcessingMode{
					RequestHeaderMode:   "INVALID",
					ResponseHeaderMode:  "INVALID",
					RequestBodyMode:     "INVALID",
					ResponseBodyMode:    "INVALID",
					RequestTrailerMode:  "INVALID",
					ResponseTrailerMode: "INVALID",
				},
			},
			validateResult: func(t *testing.T, result *envoy_ext_proc_v3.ExtProcPerRoute) {
				processingMode := result.GetOverrides().GetProcessingMode()
				assert.NotNil(t, processingMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_DEFAULT, processingMode.RequestHeaderMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_DEFAULT, processingMode.ResponseHeaderMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_NONE, processingMode.RequestBodyMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_NONE, processingMode.ResponseBodyMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_DEFAULT, processingMode.RequestTrailerMode)
				assert.Equal(t, envoy_ext_proc_v3.ProcessingMode_DEFAULT, processingMode.ResponseTrailerMode)
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := translateExtProcPerFilterConfig(tt.extprocConfig)

			// require.NoError(t, err)
			require.NotNil(t, result)
			tt.validateResult(t, result)
		})
	}
}
